using System;
using System.Reflection;
using UnityEditor;
using UnityEngine;

//https://github.com/JohannesDeml/EditorAudioUtils
namespace SCore.Editor
{
    public class AudioUtilMethodWrapper
    {
        private readonly MethodInfo _method;
        private readonly object[] _parameters;

        public AudioUtilMethodWrapper(Type audioUtilClass, string methodName, Type[] callParameterTypes)
        {
            _method = audioUtilClass.GetMethod(
                methodName,
                BindingFlags.Static | BindingFlags.Public,
                null,
                callParameterTypes,
                null
            );

            _parameters = callParameterTypes.Length > 0 ? new object[callParameterTypes.Length] : Array.Empty<object>();
        }

        public object Invoke()
        {
            if (_parameters.Length != 0)
            {
                throw new Exception(
                    $"Called with the wrong number of arguments. Expected {_parameters.Length}, Actual: 0");
            }

            return _method.Invoke(null, _parameters);
        }

        public object Invoke(object arg0)
        {
            if (_parameters.Length != 1)
            {
                throw new Exception(
                    $"Called with the wrong number of arguments. Expected {_parameters.Length}, Actual: 1");
            }

            _parameters[0] = arg0;
            return _method.Invoke(null, _parameters);
        }

        public object Invoke(object arg0, object arg1)
        {
            if (_parameters.Length != 2)
            {
                throw new Exception(
                    $"Called with the wrong number of arguments. Expected {_parameters.Length}, Actual: 2");
            }

            _parameters[0] = arg0;
            _parameters[1] = arg1;
            return _method.Invoke(null, _parameters);
        }

        public object Invoke(object arg0, object arg1, object arg2)
        {
            if (_parameters.Length != 3)
            {
                throw new Exception(
                    $"Called with the wrong number of arguments. Expected {_parameters.Length}, Actual: 3");
            }

            _parameters[0] = arg0;
            _parameters[1] = arg1;
            _parameters[2] = arg2;
            return _method.Invoke(null, _parameters);
        }
    }

    public class EditorAudioUtil
    {
        public static AudioClip LastPlayedPreviewClip { get; private set; }
        private static bool _initialized;
        private static AudioUtilMethodWrapper _playPreviewClip;
        private static AudioUtilMethodWrapper _pausePreviewClip;
        private static AudioUtilMethodWrapper _resumePreviewClip;
        private static AudioUtilMethodWrapper _stopPreviewClip;
        private static AudioUtilMethodWrapper _stopAllPreviewClips;
        private static AudioUtilMethodWrapper _isPreviewClipPlaying;


        /// <summary>
        /// Play an audio clip in the editor once
        /// </summary>
        /// <param name="audioClip">Audio clip to play</param>
        public static void PlayPreviewClip(AudioClip audioClip)
        {
            PlayPreviewClip(audioClip, 0, false);
        }

        /// <summary>
        /// Play an audio clip in the editor
        /// </summary>
        /// <param name="audioClip">Clip to play</param>
        /// <param name="startSample">The sample to start playing the clip</param>
        /// <param name="loop"></param>
        public static void PlayPreviewClip(AudioClip audioClip, int startSample, bool loop)
        {
            if (!_initialized)
            {
                Initialize();
            }

            LastPlayedPreviewClip = audioClip;
            _playPreviewClip.Invoke(audioClip, startSample, loop);
        }

        /// <summary>
        /// Pause the currently playing preview clips (Unity 2020+) or the defined clip (Unity 2019)
        /// </summary>
        /// <param name="audioClip">Clip to pause (Unity 2019) - Ignored in Unity 2020+</param>
        public static void PausePreviewClip(AudioClip audioClip)
        {
            if (!_initialized)
            {
                Initialize();
            }

#if UNITY_2020_1_OR_NEWER
            _pausePreviewClip.Invoke();
#else
			_pausePreviewClip.Invoke(audioClip);
#endif
        }

        /// <summary>
        /// Resume the paused preview clips (Unity 2020+) or the defined clip (Unity 2019)
        /// </summary>
        /// <param name="audioClip">Clip to resume (Unity 2019) - Ignored in Unity 2020+</param>
        public static void ResumePreviewClip(AudioClip audioClip)
        {
            if (!_initialized)
            {
                Initialize();
            }

#if UNITY_2020_1_OR_NEWER
            _resumePreviewClip.Invoke();
#else
			_resumePreviewClip.Invoke(audioClip);
#endif
        }

        /// <summary>
        /// Stop all preview clips (Unity 2020+) or the defined clip (Unity 2019)
        /// </summary>
        /// <param name="audioClip">Clip to stop (Unity 2019) - Ignored in Unity 2020+</param>
        public static void StopPreviewClip(AudioClip audioClip)
        {
            if (!_initialized)
            {
                Initialize();
            }

#if UNITY_2020_1_OR_NEWER
            _stopPreviewClip.Invoke();
#else
			_stopPreviewClip.Invoke(audioClip);
#endif
        }

        /// <summary>
        /// Stop all playing preview clips
        /// </summary>
        public static void StopAllPreviewClips()
        {
            if (!_initialized)
            {
                Initialize();
            }

            _stopAllPreviewClips.Invoke();
        }

        /// <summary>
        /// Is any preview clip playing (Unity 2020+) or the defined clip playing (Unity 2019)
        /// </summary>
        /// <param name="audioClip">Clip to check if it is playing (Unity 2019) - Ignored in Unity 2020+</param>
        /// <returns>True if audioClip is playing (Unity 2019) or any clip is playing (Unity 2020+=</returns>
        public static bool IsPreviewClipPlaying(AudioClip audioClip)
        {
            if (!_initialized)
            {
                Initialize();
            }

#if UNITY_2020_1_OR_NEWER
            return (bool)_isPreviewClipPlaying.Invoke();
#else
			return (bool)_isPreviewClipPlaying.Invoke(audioClip);
#endif
        }

        private static void Initialize()
        {
            Assembly unityEditorAssembly = typeof(AudioImporter).Assembly;
            Type audioUtilClass = unityEditorAssembly.GetType("UnityEditor.AudioUtil");

#if UNITY_2020_1_OR_NEWER
            _playPreviewClip = new AudioUtilMethodWrapper(audioUtilClass, "PlayPreviewClip",
                new[] { typeof(AudioClip), typeof(int), typeof(bool) });
            _pausePreviewClip = new AudioUtilMethodWrapper(audioUtilClass, "PausePreviewClip",
                Array.Empty<Type>());
            _resumePreviewClip = new AudioUtilMethodWrapper(audioUtilClass, "ResumePreviewClip",
                Array.Empty<Type>());
            _stopPreviewClip = new AudioUtilMethodWrapper(audioUtilClass, "StopAllPreviewClips",
                Array.Empty<Type>());
            _stopAllPreviewClips = new AudioUtilMethodWrapper(audioUtilClass, "StopAllPreviewClips",
                Array.Empty<Type>());
            _isPreviewClipPlaying = new AudioUtilMethodWrapper(audioUtilClass, "IsPreviewClipPlaying",
                Array.Empty<Type>());
#else
			_playPreviewClip = new AudioUtilMethodWrapper(audioUtilClass, "PlayClip",
				new[] {typeof(AudioClip), typeof(int), typeof(bool)} );
			_pausePreviewClip = new AudioUtilMethodWrapper(audioUtilClass, "PauseClip",
				new[] {typeof(AudioClip)});
			_resumePreviewClip = new AudioUtilMethodWrapper(audioUtilClass, "ResumeClip",
				new[] {typeof(AudioClip)});
			_isPreviewClipPlaying = new AudioUtilMethodWrapper(audioUtilClass, "IsClipPlaying",
				new[] {typeof(AudioClip)});
			_stopPreviewClip = new AudioUtilMethodWrapper(audioUtilClass, "StopClip",
				new[] {typeof(AudioClip)});
			_stopAllPreviewClips = new AudioUtilMethodWrapper(audioUtilClass, "StopAllClips",
				Array.Empty<Type>());
#endif

            _initialized = true;
        }
    }
}